</center> Автор материала: Илья Барышников. Материал распространяется на условиях лицензии Creative Commons CC BY-NC-SA 4.0. Можно использовать в любых целях (редактировать, поправлять и брать за основу), кроме коммерческих, но с обязательным упоминанием автора материала.
В задании предлагается с помощью визуального анализа ответить на несколько вопросов по данным о сердечно-сосудистых заболеваниях. Данные использовались в соревновании Ml Boot Camp 5 (качать их не надо, они уже есть в репозитории).
Заполните код в клетках (где написано "Ваш код здесь") и ответьте на вопросы в веб-форме.
В соревновании предлагалось определить наличие/отсутствие сердечно-сосудистых заболеваний (ССЗ) по результатам осмотра пациента.
Описание данных.
Объективные признаки:
Результаты измерения:
Субъективные признаки (со слов пациентов):
Целевой признак (который интересно будет прогнозировать):
Значения показателей холестерина и глюкозы представлены одним из трех классов: норма, выше нормы, значительно выше нормы. Значения субъективных признаков — бинарны.
Все показатели даны на момент осмотра.
In [2]:
# подгружаем все нужные пакеты
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib
import matplotlib.pyplot as plt
import matplotlib.ticker
%matplotlib inline
# настройка внешнего вида графиков в seaborn
sns.set_context(
"notebook",
font_scale = 1.5,
rc = {
"figure.figsize" : (12, 9),
"axes.titlesize" : 18
}
)
In [3]:
train = pd.read_csv('../../data/mlbootcamp5_train.csv', sep=';',
index_col='id')
In [4]:
print('Размер датасета: ', train.shape)
train.head()
Out[4]:
Для начала всегда неплохо бы посмотреть на значения, которые принимают переменные.
Переведем данные в "Long Format"-представление и отрисуем с помощью factorplot количество значений, которые принимают категориальные переменные.
In [5]:
train_uniques = pd.melt(frame=train, value_vars=['gender','cholesterol',
'gluc', 'smoke', 'alco',
'active', 'cardio'])
train_uniques = pd.DataFrame(train_uniques.groupby(['variable',
'value'])['value'].count()) \
.sort_index(level=[0, 1]) \
.rename(columns={'value': 'count'}) \
.reset_index()
sns.factorplot(x='variable', y='count', hue='value',
data=train_uniques, kind='bar', size=12);
Видим, что классы целевой переменно сбалансированы, отлично!
Можно также разбить элементы обучающей выборки по значениям целевой переменной: иногда на таких графиках можно сразу увидеть самый значимый признак.
In [6]:
train_uniques = pd.melt(frame=train, value_vars=['gender','cholesterol',
'gluc', 'smoke', 'alco',
'active'],
id_vars=['cardio'])
train_uniques = pd.DataFrame(train_uniques.groupby(['variable', 'value',
'cardio'])['value'].count()) \
.sort_index(level=[0, 1]) \
.rename(columns={'value': 'count'}) \
.reset_index()
sns.factorplot(x='variable', y='count', hue='value',
col='cardio', data=train_uniques, kind='bar', size=9);
Видим, что в зависимости от целевой переменной сильно меняется распределение холестерина и глюкозы. Совпадение?
Немного статистики по уникальным значениям признаков.
In [6]:
for c in train.columns:
n = train[c].nunique()
print(c)
if n <= 3:
print(n, sorted(train[c].value_counts().to_dict().items()))
else:
print(n)
print(10 * '-')
Итого:
Для того, чтобы лучше понять признаки в датасете, можно посчитать матрицу коэффициентов корреляции между признаками.
Постройте heatmap корреляционной матрицы. Матрица формируется средствами pandas, со стандартным значением параметров.
In [9]:
corr_matrix = train.drop(['age', 'ap_hi', 'ap_lo',
'gluc', 'active'], axis=1).corr()
sns.heatmap(corr_matrix, annot=True)
Out[9]:
Как мы увидели, в процессе исследования уникальных значений, пол кодируется значениями 1 и 2, расшифровка изначально не была нам дана в описании данных, но мы догадались, кто есть кто, посчитав средние значения роста (или веса) при разных значениях признака gender
. Теперь сделаем то же самое, но графически.
Постройте violinplot для роста и пола. Используйте:
Для корректной отрисовки, преобразуйте DataFrame в "Long Format"-представление с помощью функции melt в pandas.
еще один пример
Постройте на одном графике два отдельных kdeplot роста, отдельно для мужчин и женщин. На нем разница будет более наглядной, но нельзя будет оценить количество мужчин/женщин.
In [8]:
train.head()
Out[8]:
In [4]:
filtered_df = train[(train['ap_lo'] <= train['ap_hi']) &
(train['height'] >= train['height'].quantile(0.025)) &
(train['height'] <= train['height'].quantile(0.975)) &
(train['weight'] >= train['weight'].quantile(0.025)) &
(train['weight'] <= train['weight'].quantile(0.975))]
df = pd.melt(filtered_df, value_vars=['weight'], id_vars='gender')
sns.violinplot(x='variable', y='value', hue='gender', data=df)
Out[4]:
In [6]:
female = filtered_df[filtered_df['gender'] == 1]
male = filtered_df[filtered_df['gender'] == 2]
ax = sns.kdeplot(female.height, female.height,
cmap="Reds", shade=True, shade_lowest=False)
ax = sns.kdeplot(male.height, male.height,
cmap="Blues", shade=True, shade_lowest=False)
В большинстве случаев достаточно воспользоваться линейным коэффициентом корреляции Пирсона для выявления закономерностей в данных, но мы пойдем чуть дальше и используем ранговую корреляцию, которая поможет нам выявить пары, в которых меньший ранг из вариационного ряда одного признака всегда предшествует большему другого (или наоборот, в случае отрицательной корреляции).
Постройте корреляционную матрицу, используя коэффициент Спирмена
In [12]:
filtered_df = train[(train['ap_lo'] <= train['ap_hi']) &
(train['height'] >= train['height'].quantile(0.025)) &
(train['height'] <= train['height'].quantile(0.975)) &
(train['weight'] >= train['weight'].quantile(0.025)) &
(train['weight'] <= train['weight'].quantile(0.975))]
corr_matrix = filtered_df.drop(['gender', 'active'], axis=1).corr(method = 'spearman')
sns.heatmap(corr_matrix, annot=True)
Out[12]:
In [11]:
mybins=np.logspace(0,np.log(100),100)
data = sns.load_dataset('tips')
g = sns.JointGrid('total_bill', 'tip', data,xlim=[1,100],ylim=[0.01,100])
g.plot_marginals(sns.distplot, hist=True, kde=True, color='blue',bins=mybins)
g.plot_joint(plt.scatter, color='black', edgecolor='black')
ax = g.ax_joint
ax.set_xscale('log')
ax.set_yscale('log')
g.ax_marg_x.set_xscale('log')
g.ax_marg_y.set_yscale('log')
Постройте совместный график распределения jointplot двух наиболее коррелирующих между собой признаков (по Спирмену).
Кажется, наш график получился неинформативным из-за выбросов в значениях. Постройте тот же график, но с логарифмической шкалой.
In [3]:
g = sns.JointGrid('ap_lo', 'ap_hi', train,xlim=[1,100000],ylim=[0.01,100000])
g.plot_marginals(sns.distplot, hist=True, kde=True, color='blue')
g.plot_joint(plt.scatter, color='black', edgecolor='black')
ax = g.ax_joint
ax.set_xscale('log')
ax.set_yscale('log')
g.ax_marg_x.set_xscale('log')
g.ax_marg_y.set_yscale('log')
"""Сетка"""
g.ax_joint.grid(True)
"""Преобразуем логарифмические значения на шкалах в реальные"""
#g.ax_joint.yaxis.set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, pos: str(x) if x == 0 else str(round(int(np.exp(x))))))
#g.ax_joint.xaxis.set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, pos: str(x) if x == 0 else str(round(int(np.exp(x))))))
Out[3]:
In [12]:
clear_ds = train[
(train['ap_lo'] > 0)
& (train['ap_hi'] > 0)
& (train['ap_hi'] >= train['ap_hi'].quantile(0.025))
& (train['ap_hi'] <= train['ap_hi'].quantile(0.975))
& (train['ap_lo'] >= train['ap_lo'].quantile(0.025))
& (train['ap_lo'] <= train['ap_lo'].quantile(0.975))
]
clear_ds['ap_lo_log'] = np.log1p(clear_ds['ap_lo'])
clear_ds['ap_hi_log'] = np.log1p(clear_ds['ap_hi'])
g = sns.jointplot("ap_lo_log", "ap_hi_log", data=clear_ds)
"""Сетка"""
g.ax_joint.grid(True)
"""Преобразуем логарифмические значения на шкалах в реальные"""
g.ax_joint.yaxis.set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, pos: str(round(int(np.exp(x))))))
g.ax_joint.xaxis.set_major_formatter(matplotlib.ticker.FuncFormatter(lambda x, pos: str(round(int(np.exp(x))))))
In [ ]:
# Ваш код здесь
Посчитаем, сколько полных лет было респондентам на момент их занесения в базу.
In [5]:
train['age_years'] = (train['age'] // 365.25).astype(int)
Постройте Countplot, где на оси абсцисс будет отмечен возраст, на оси ординат – количество. Каждое значение возраста должно иметь два столбца, соответствующих количеству человек каждого класса cardio данного возраста.
In [6]:
sns.countplot(x="age_years", hue="cardio", data=train)
Out[6]: